#ifndef PPU2_H
#define PPU2_H

#include "cartridge.h"
#include "palette.h"
#include <QDataStream>

class PPU
{
    friend QDataStream &operator<<(QDataStream &stream, const PPU &Ppu); // 序列化
    friend QDataStream &operator>>(QDataStream &stream, PPU &Ppu);       // 反序列化
public:
    PPU();
    ~PPU();

private:
    quint8 tblName[2][1024]; // 名称表（包含属性表）
    quint8 tblPalette[32];   // 调色板（共8个，4个用于背景，4个用于精灵）

public:
    quint8 frame_data[256][240][3];  // 存储每个像素的RGB
    bool frame_complete = false;     // 一帧渲染完毕标志

private:
    union PPUSTATUS {
        struct
        {
            quint8 unused : 5;
            quint8 sprite_overflow : 1; // 精灵溢出标志
            quint8 sprite_zero_hit : 1; // 精灵0冲突标志
            quint8 vertical_blank : 1;  // 垂直消隐阶段标志
        };

        quint8 reg;
    } status;

    union PPUMASK {
        struct
        {
            quint8 grayscale : 1;              // 是否开启灰白模式
            quint8 render_background_left : 1; // 是否显示背景最左8个像素
            quint8 render_sprites_left : 1;    // 是否显示精灵最左8个像素
            quint8 render_background : 1;      // 是否渲染背景
            quint8 render_sprites : 1;         // 是否渲染精灵
            quint8 enhance_red : 1;            // 以下三位为扩展，基础游戏用不到
            quint8 enhance_green : 1;
            quint8 enhance_blue : 1;
        };

        quint8 reg;
    } mask;

    union PPUCTRL {
        struct
        {
            quint8 nametable_x : 1;
            quint8 nametable_y : 1;
            quint8 increment_mode : 1; // 该变量用于控制ppu_address自增；换行时+32，否则+1
            quint8 pattern_sprite : 1;
            quint8 pattern_background : 1;
            quint8 sprite_size : 1; // 精灵可能为8x8或8x16大小
            quint8 slave_mode : 1;  // unused
            quint8 enable_nmi : 1;  // 是否启用nmi
        };

        quint8 reg;
    } control;

    // 该结构的定义与详细解释见以下文件
    // http://nesdev.com/loopyppu.zip
    union loopy_register {
        struct
        {
            // coarse_y和coarse_x用于表示瓦片在名称表中的偏移量
            // 它们等价于y*width+x
            quint16 coarse_x : 5;
            quint16 coarse_y : 5;

            // 根据镜像方式的不同，最多可以寻址4个名称表
            // 所以需要两位来标记所在名称表
            quint16 nametable_x : 1;
            quint16 nametable_y : 1;

            // 因为一个瓦片是8*8像素
            // 所以我们需要3位去确定一个瓦片内的纵向精确偏移量
            // 横向精确偏移量定义在该结构体以外
            quint16 fine_y : 3;
            quint16 unused : 1;
        };
        // 根据上面的描述，当逐像素渲染时，首先fine_x++
        // 当fine_x>7时，说明切换到下一个瓦片了，则coarse_x++
        // 同理，当切换到下一行时，fine_y++
        // 当fine_y>7时，切换瓦片，则coarse_y++
        // 随着渲染进行，我们不可避免的会切换名称表
        // 此时应对nametable_x(y)进行修改（翻转即可）

        quint16 reg = 0x0000;
    };

    // 之所以需要两个，具体可见：http://wiki.nesdev.com/w/index.php/PPU_scrolling
    // vram是PPU内部的，用户不应直接对其进行修改
    // 但在某些时候需要从tram中交换信息给vram
    loopy_register vram_addr;

    // tram是面向用户的，在读写ctrl、addr时都应该更新
    loopy_register tram_addr;

    // 一个瓦片内部的精确横向偏移
    quint8 fine_x = 0x00;

    bool address_latch = false; // 地址锁存器。用以区分写入高6位，还是低8位

    // 多数情况，CPU读取PPU数据时需要延迟一个周期，故使用buffer暂存
    // 除了读取调色板，在read_data函数中有所体现
    quint8 ppu_data_buffer = 0x00;

    // scanline和cycle确认了当前渲染的某一像素
    int16_t scanline = 0;   // 扫描线，相当于y
    int16_t cycle = 0;      // 周期，相当于x
    bool odd_frame = false; // 记录当前位奇偶周期，DMA时有不同

    // Background rendering =========================================
    quint8 bg_next_tile_id = 0x00;
    quint8 bg_next_tile_attrib = 0x00;

    // 瓦片上任意像素可以有4种颜色，使用2位即可表示
    // 2位可进一步分为lsb(least significant bit)和msb(most significant bit)
    // 这样处理的目的是减少移位操作。一块瓦片由16字节表示
    quint8 bg_next_tile_lsb = 0x00;
    quint8 bg_next_tile_msb = 0x00;

    // shifter将存储当前渲染的8个像素，与即将渲染的8个像素信息
    quint16 bg_shifter_pattern_lo = 0x0000;
    quint16 bg_shifter_pattern_hi = 0x0000;
    quint16 bg_shifter_attrib_lo = 0x0000;
    quint16 bg_shifter_attrib_hi = 0x0000;

    // Foreground "Sprite" rendering ================================
    // 一个精灵的4字节分别对应4个信息，OAM可以存储64个精灵
    struct sObjectAttributeEntry
    {
        quint8 y;         // 纵坐标
        quint8 id;        // 图案表中的瓦片ID
        quint8 attribute; // 属性标志
        quint8 x;         // 横坐标
    } OAM[64];

    // 存储CPU访问OAM的地址
    quint8 oam_addr = 0x00;

    // 每行仅加载8个精灵
    sObjectAttributeEntry spriteScanline[8]; // 暂存一行需要渲染的精灵
    quint8 sprite_count;                     // 一行真实需要渲染的精灵数，不超过8
    quint8 sprite_shifter_pattern_lo[8]; // 我们需要存储一行中所有需要渲染精灵的
    quint8 sprite_shifter_pattern_hi[8]; // 信息，所以建[8]个

    // Sprite Zero Collision Flags
    // 0Hit的意思是，精灵0不透明像素和背景不透明像素重叠
    bool bSpriteZeroHitPossible = false;
    bool bSpriteZeroBeingRendered = false;

public:
    // 开放给CPU读取OAM的入口
    quint8 *pOAM = (quint8 *) OAM;

private:
    // 渲染阶段相关函数（用于clock）
    void IncrementScrollX();       // 使瓦片“指针”横向自增
    void IncrementScrollY();       // 使瓦片“指针”纵向自增
    void TransferAddressX();       // 将tram中的X相关信息拷贝给vram
    void TransferAddressY();       // 将tram中的Y相关信息拷贝给vram
    void LoadBackgroundShifters(); // 初始化shfiter
    void UpdateShifters(); // 每个周期渲染一个像素，将shifter左移1位，使已渲染的像素出队

public:
    // 与CPU总线交互的函数
    // read
    quint8 get_status();
    quint8 get_oamdata();
    quint8 read_data();

    // write
    void write_ctrl(quint8 ctrl);
    void write_mask(quint8 mask);
    void write_scroll(quint8 scroll);
    void write_addr(quint8 addr);
    void write_data(quint8 data);

    void write_oamaddr(quint8 addr);
    void write_oamdata(quint8 data);

    // 与PPU总线交互的函数
    quint8 ppuRead(quint16 addr);
    void ppuWrite(quint16 addr, quint8 data);

private:
    // 将PPU总线与卡带连通
    Cartridge *cart;

public:
    // 对外接口
    void ConnectCartridge(Cartridge *cartridge);
    void clock();
    void reset();
    bool nmi = false;
};

#endif // PPU2_H
